import static akka.actor.Actors.actorOf;

import akka.actor.ActorRef;
import akka.actor.UntypedActor;
import akka.actor.UntypedActorFactory;

/**
 * Program that runs the solved Cigarette Smokers Problem.
 * 
 * @author Peter-John Rowe
 *
 */
public class Main {

	public static final String NEW_LINE = "\n";

	/**The number of times the Arbiter places materials on the table*/
	private final int NUMBER_OF_ROUNDS;
	/**The number of smokers around the table*/
	private final int NUMBER_OF_SMOKERS = 3;
	/**The Arbiter who supplies the ingredients for the cigarettes*/
	private final ActorRef supplier;
	/**The table that the ingredients are placed on*/
	private final ActorRef table;
	/**Smokers obtaining the ingredients to make cigarettes*/
	private final ActorRef[] smokers;

	/**
	 * Main constructor that takes in the number of rounds from command line.
	 * 
	 * @param nRounds - Number of rounds 
	 */
	public Main(int rounds) {

		NUMBER_OF_ROUNDS = rounds;

		smokers = new ActorRef[NUMBER_OF_SMOKERS];

		table = actorOf(new TableFactory(NUMBER_OF_ROUNDS,NUMBER_OF_SMOKERS));

		for (ActorRef smoker:smokers) {
			smoker = actorOf(new SmokerFactory(table));
			((Table) table.actor()).addSmoker(smoker);
		}

		supplier = actorOf(new ArbiterFactory(table,smokers));
		((Table) table.actor()).setArbiter(supplier);

	}

	/**
	 * Initiate the actors.
	 * @throws InterruptedException 
	 */
	public void start() throws InterruptedException {
		supplier.start();
		for (ActorRef smoker:smokers) {
			smoker.start();
		}

		supplier.tell(new StartMessage());
	}

	public void stop() {
		supplier.tell(new StopMessage());
	}

	/**
	 * Main Method.
	 * 
	 * @param args
	 */
	public static void main(String[] args) {

		try {
			// Parse command line argument
			int rounds = Integer.parseInt(args[0]);

			// If number of command line arguments is not 1
			if ( args.length != 1 ) {
				throw new Exception("Usage: java Main nRounds");
				// If number of rounds input is less than 1
			} else if ( rounds < 1 ) {
				throw new Exception("nRounds must be at least 1");
				// Else start the program
			} else {
				Main driver = new Main(rounds);
				driver.start();
				Thread.sleep(1000);
				driver.stop();
			}
		} catch (NumberFormatException ex) {
			System.err.println("nRounds should be an integer");
		} catch (Exception e) {
			System.err.println(e.getMessage());
		}
	}

	/**
	 * ArbiterFactory class.
	 * 
	 * @author Peter-John Rowe
	 */
	static class ArbiterFactory implements UntypedActorFactory{

		/**ActorRef to Table actor*/
		private ActorRef tableRef;
		/**Array of Smokers*/
		private ActorRef[] smokersArray;

		/**
		 * ArbiterFactory constructor.
		 * 
		 * @param table
		 */
		public ArbiterFactory(ActorRef table, ActorRef[] smokers) {  
			tableRef = table;
			smokersArray = smokers;
		}

		/**
		 * Returns a new Arbiter actor.
		 */
		public UntypedActor create() { return new Arbiter(tableRef,smokersArray); }
	}

	/**
	 * Factory to create Smoker actors.
	 * 
	 * @author Peter-John Rowe
	 */
	static class SmokerFactory implements UntypedActorFactory{

		/**Reference to table actor*/
		private ActorRef tableRef;

		/**
		 * SmokerFactory constructor
		 * 
		 * @param table
		 */
		public SmokerFactory(ActorRef table) { 
			tableRef = table;
		}

		/**
		 * Returns a new smoker actor.
		 */
		public UntypedActor create() { return new Smoker(tableRef); }
	}

	/**
	 * Factory to create Table actors.
	 * 
	 * @author Peter-John Rowe
	 */
	static class TableFactory implements UntypedActorFactory{

		/**Number of smokers*/
		private int numOfSmokers;

		private int numOfRounds;
		/**
		 * TableFactory constructor.
		 * 
		 * @param numOfSmokers
		 */
		public TableFactory(int numOfRounds,int numOfSmokers) { 
			this.numOfRounds = numOfRounds;
			this.numOfSmokers = numOfSmokers;
		}

		/**
		 * Create method to return new Table
		 */
		public UntypedActor create() { return new Table(numOfRounds,numOfSmokers); }
	}

} // Main Class
